/*
 * @(#)URL.java	1.45 2001/12/12
 *
 * 版权所有2002 Sun Microsystems公司版权所有。太阳专有/保密。使用受许可条款的约束。
 */

package java.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.StringTokenizer;

/**
 * Class <code>URL</code>表示一个统一资源定位器，一个指向万维网上“资源”的指针。
 * 资源可以是文件或目录这样简单的东西，也可以是对更复杂对象的引用，比如对数据库或搜索引擎的查询。更多的
 * 有关网址类型及其格式的资料，可于以下网址找到:
 * <ul><code>
 *     http://www.ncsa.uiuc.edu/demoweb/url-primer.html
 * </code></ul>
 * <p>
 * 一般来说，URL可以分为几个部分。前面的URL示例表明要使用的协议是<code>http</code>(超文本传输协议)，并且该信息驻留在名为
 * <代码> www.ncsa.uiuc.edu > < /代码。主机上的信息被命名为<code>demoweb/url-primer
 * .html</code>。这个名称在主机上的确切含义取决于协议和主机。信息通常驻留在文件中，但也可以动态生成。这个组成部分
 * URL被称为<i>文件</i>组件，即使信息不一定在文件中。
 * <p>
 * URL 可以选择指定“端口”，这是远程主机上建立 TCP 连接的端口号。如果未指定端口，则改用协议的默认端口。
 * 例如，<code>http</code> 的默认端口为 <code>80</code>。备用端口可以指定为：
 * <ul><code>
 *     http://www.ncsa.uiuc.edu:8080/demoweb/url-primer.html
 * </code></ul>
 * <p>
 * URL 可能附加了一个“锚点”，也称为“引用”或“引用”。锚点由尖符号字符“#”指示，后跟更多字符。例如
 * <ul><code>
 *     http://java.sun.com/index.html#chapter1
 * </code></ul>
 * <p>
 * 从技术上讲，此定位点不是 URL 的一部分。相反，它指示在检索指定资源后，
 * 应用程序对附加了标记 <code>chapter1</code> 的文档部分特别感兴趣。标签的含义是特定于资源的。
 * <p>
 * 应用程序还可以指定“相对 URL”，该 URL 仅包含足够的信息以访问相对于另一个 URL 的资源。
 * 相对 URL 经常在 HTML 页面中使用。例如，如果网址的内容：
 * <ul><code>
 *     http://java.sun.com/index.html
 * </code></ul>
 * contained within it the relative URL:
 * <ul><code>
 *     FAQ.html
 * </code></ul>
 * it would be a shorthand for:
 * <ul><code>
 *     http://java.sun.com/FAQ.html
 * </code></ul>
 * <p>
 * 相对 URL 不需要指定 URL 的所有组件。如果缺少协议、主机名或端口号，则该值将从完全指定的 URL 继承。
 * 必须指定文件组件。可选定位点不继承。
 *
 * @author  James Gosling
 * @version 1.45, 12/12/01
 * @since   JDK1.0
 */
public final class URL implements java.io.Serializable {
    /**
     * 指定要扫描协议处理程序的包前缀列表的属性。 此属性的值（如果有）应为垂直条分隔的包名称列表，以搜索要加载的协议处理程序。
	 * 此类的策略是所有协议处理程序都将位于名为 的类中<protocolname>。处理程序，并依次检查列表中的每个包以查找匹配的处理程序。
	 * 如果未找到任何属性（或未指定属性），则使用默认包前缀 sun.net.www.protocol。
	 * 搜索从最后一个包中的第一个包到最后一个包进行，并在找到匹配项时停止。
     */
    private static final String protocolPathProp = "java.protocol.handler.pkgs";

    /** 
     * The protocol to use (ftp, http, nntp, ... etc.) . 
     */
    private String protocol;

    /** 
     * The host name in which to connect to. 
     */
    private String host;

    /** 
     * The protocol port to connect to. 
     */
    private int port = -1;

    /** 
     * The specified file name on that host. 
     */
    private String file;

    /** 
     * # reference. 
     */
    private String ref;

    /**
     * The URLStreamHandler for this URL.
     */
    transient URLStreamHandler handler;

    /* Our hash code */
    private int hashCode = -1;

    /** 
     * 从指定的<code>协议</code>， <code>主机</code>， <code>端口</code>编号，<code>文件</code>创建<code>URL</code>对象。
	 * 指定<code>port</code> number <code>-1</code>表示URL应该使用协议的默认端口。
     * <p>
     * 如果这是使用指定协议创建的第一个URL对象，则为该协议创建一个<i>流协议处理程序</i>对象，类<code>URLStreamHandler</code>的实例:
     * <ol>
     * <li>如果应用程序之前设置了<code>URLStreamHandlerFactory</code>的实例作为流处理程序工厂，
	 * 那么该实例的<code>createURLStreamHandler</code>方法将被调用，并将协议字符串作为参数来创建流协议处理程序。
     * <li>如果没有<code>URLStreamHandlerFactory</code>还没有设置，
	 * 或者如果工厂的<code>createURLStreamHandler</code>方法返回<code>null</code>，
	 * 那么构造函数会找到system属性的值:
     *     <ul><code>
     *         java.handler.protol.pkgs
     *     </code></ul>
     *     如果该系统属性的值不是<code>null</code>，它将被解释为一个由垂直斜杠字符'<code>|</code>'分隔的包列表。
	 *     构造函数尝试加载类命名为:
     *     <ul><code>
     *         &lt;<i>package</i>&gt;.&lt;<i>protocol</i>&gt;.Handler
     *     </code></ul>
     *     其中&lt<i>程序包</i>&gt；被包的名称替换，并且&lt<i> 协议</i>&gt；替换为协议的名称。
	 *     如果该类不存在，或者该类存在，但它不是URLStreamHandler</code>的子类，则下一个包在列表中尝试。
     * <li>如果上一步没有找到协议处理程序，构造函数就会尝试加载命名为:
     *     <ul><code>
     *         sun.net.www.protocol.&lt;<i>protocol</i>&gt;.Handler
     *     </code></ul>
     *     如果该类不存在，或者该类存在但它不是<code>URLStreamHandler</code>的子类，则抛出<code>MalformedURLException</code>。
     * </ol>
     *
     * @param      protocol   the name of the protocol.
     * @param      host       the name of the host.
     * @param      port       the port number.
     * @param      file       the host file.
     * @exception  MalformedURLException  if an unknown protocol is specified.
     * @see        System#getProperty(String)
     * @see        URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)
     * @see        URLStreamHandler
     * @see        URLStreamHandlerFactory#createURLStreamHandler(String)
     * @since      JDK1.0
     */
    public URL(String protocol, String host, int port, String file)
	throws MalformedURLException {
	this.protocol = protocol;
	this.host = host;
	this.port = port;
	int ind = file.indexOf('#');
	this.file = ind < 0 ? file: file.substring(0, ind);
	this.ref = ind < 0 ? null: file.substring(ind + 1);
	if ((handler = getURLStreamHandler(protocol)) == null) {
	    throw new MalformedURLException("unknown protocol: " + protocol);
	}
    }

    /** 
     * 从指定的<code>协议</code>名称、<code>主机</code>名称和<code>文件</code>名称创建一个绝对URL。使用指定协议的默认端口。
     * <p>
     * 此方法等效于调用包含四个参数的构造函数，参数分别为<code>协议</code>， <code>主机</code>， <code>-1</code>和<code>文件</code>。
     *
     * @param      protocol   the protocol to use.
     * @param      host       the host to connect to.
     * @param      file       the file on that host.
     * @exception  MalformedURLException  if an unknown protocol is specified.
     * @see        URL#URL(String, String, int, String)
     * @since      JDK1.0
     */
    public URL(String protocol, String host, String file) throws MalformedURLException {
	this(protocol, host, -1, file);
    }

    /**
     * 这个构造函数等价于调用带有<code>null</code>第一个参数的双参数构造函数。
     * <p>
     * 这个构造函数等价于调用带有<code>null</code>第一个参数的双参数构造函数。
     *
     * @param      spec   the <code>String</code> to parse as a URL.
     * @exception  MalformedURLException  If the string specifies an
     *               unknown protocol.
     * @see        URL#URL(URL, String)
     * @since      JDK1.0
     */
    public URL(String spec) throws MalformedURLException {
	this(null, spec);
    }

    /** 
     * 通过解析指定上下文中的规范<code>spec</code>来创建URL。
	 * 如果<code>context</code>参数不是<code>null</code>，而<code>spec</code>变量是部分URL规范，
	 * 则任何缺少组件的字符串都会从<code>context</code>中继承。
     * <p>
     * <code>String</code>参数给出的规范被解析以确定它是否指定了协议。
	 * 如果<code>String</code>在第一次出现ASCII斜杠字符“<code>/</code>”之前包含一个ASCII冒号“<code>：</code>“字符，
	 * 则冒号之前的字符构成协议。
     * <ul>
     * <li>如果<code>spec</code>参数没有指定协议:
     *     <ul>
     *     <li>如果context参数不为空，则从context参数复制协议。
     *     <li>如果context参数为<code>null</code>，则抛出<code>MalformedURLException</code>。
     *     </ul>
     * <li>如果<code>spec</code>参数指定了协议:
     *     <ul>
     *     <li>如果上下文参数为＜code＞null＜/code＞，或者指定了与规范参数不同的协议，则忽略上下文参数。
     *     <li>如果context参数不是<code>null</code>，并且指定的协议与规范相同，则从<code>主机</code>，
	 *     <code>端口</code>编号，<code>文件</code> 将context参数输入到新创建的<code>URL</code>。</ul >
     * </ul>
     * <p>
     * 构造函数然后搜索类型为<code>URLStreamHandler</code>的适当流协议处理程序，如下所示:
     * <ul><code>
     *     java.net.URL#URL(java.lang.String, java.lang.String, int,
     *                      java.lang.String)
     * </code></ul>
     * 流协议处理程序的<code>parseURL</code>方法被调用来解析规范的其余字段，这些字段覆盖了context参数设置的任何默认值。

     * @param      context   the context in which to parse the specification.
     * @param      spec      a <code>String</code> representation of a URL.
     * @exception  MalformedURLException 如果未指定协议，或发现未知协议。
     * @see        URL#URL(String, String, int, String)
     * @see        URLStreamHandler
     * @see        URLStreamHandler#parseURL(URL, String, int, int)
     * @since   JDK1.0
     */
    public URL(URL context, String spec) throws MalformedURLException {
	String original = spec;
	int i, limit, c;
	int start = 0;
	String newProtocol = null;
	boolean aRef=false;

	try {
	    limit = spec.length();
	    while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
		limit--;	//eliminate trailing whitespace
	    }
	    while ((start < limit) && (spec.charAt(start) <= ' ')) {
		start++;	// eliminate leading whitespace
	    }

	    if (spec.regionMatches(true, start, "url:", 0, 4)) {
		start += 4;
	    }
	    if (start < spec.length() && spec.charAt(start) == '#') {
		/* 我们假设这是一个相对于context URL的ref。这意味着协议不能以w/ '#'开头，
		 * 但我们必须解析引用URL，例如:"hello:there"，其中w/ ':'。
		 */
		aRef=true;
	    }
	    for (i = start ; !aRef && (i < limit) && ((c = spec.charAt(i)) != '/') ; i++) {
		if (c == ':') {
		    newProtocol = spec.substring(start, i).toLowerCase();
		    start = i + 1;
		    break;
		}
	    }
	    // Only use our context if the protocols match.
	    if ((context != null) && ((newProtocol == null) ||
				    newProtocol.equals(context.protocol))) {
		protocol = context.protocol;
		host = context.host;
		port = context.port;
		file = context.file;
	    } else {
		protocol = newProtocol;
	    }

	    if (protocol == null) {
		throw new MalformedURLException("no protocol: "+original);
	    }

	    if ((handler = getURLStreamHandler(protocol)) == null) {
		throw new MalformedURLException("unknown protocol: "+protocol);
	    }

	    i = spec.indexOf('#', start);
	    if (i >= 0) {
		ref = spec.substring(i + 1, limit);
		limit = i;
	    }
	    handler.parseURL(this, spec, start, limit);

	} catch(MalformedURLException e) {
	    throw e;
	} catch(Exception e) {
	    throw new MalformedURLException(original + ": " + e);
	}
    }

    /**
     * 设置URL的字段。这不是一个公共方法，因此只有URLStreamHandlers可以修改URL字段。url在其他方面是不变的。
     *
     * REMIND: this method will be moved to URLStreamHandler
     *
     * @param protocol the protocol to use
     * @param host the host name to connecto to
     * @param port the protocol port to connect to
     * @param file the specified file name on that host
     * @param ref the reference
     */
    protected void set(String protocol, String host, int port, String file, String ref) {
	this.protocol = protocol;
	this.host = host;
	this.port = port;
	this.file = file;
	this.ref = ref;
    }

    /**
     * 返回这个<code>URL</code>的端口号。如果未设置端口，则返回-1。
     *
     * @return  the port number
     * @since   JDK1.0
     */
    public int getPort() {
	return port;
    }

    /**
     * Returns the protocol name this <code>URL</code>.
     *
     * @return  the protocol of this <code>URL</code>.
     * @since   JDK1.0
     */
    public String getProtocol() {
	return protocol;
    }

    /**
     * 如果适用，返回这个<code>URL</code>的主机名。对于"<code>file</code>"协议，这是一个空字符串。
     *
     * @return  the host name of this <code>URL</code>.
     * @since   JDK1.0
     */
    public String getHost() {
	return host;
    }

    /**
     * Returns the file name of this <code>URL</code>.
     *
     * @return  the file name of this <code>URL</code>.
     * @since   JDK1.0
     */
    public String getFile() {
	return file;
    }

    /**
     * 返回这个<code>URL</code>的锚(也称为“引用”)。
     *
     * @return  the anchor (also known as the "reference") of this
     *          <code>URL</code>.
     * @since   JDK1.0
     */
    public String getRef() {
	return ref;
    }

    /**
     * 比较两个url。当且仅当参数不<code>null</code>并且是一个<code>URL</code>对象，
	 * 表示与该对象相同的<code>URL</code>时，结果为<code>true</code>。
	 * 如果两个URL对象具有相同的协议并引用相同的主机、主机上相同的端口号以及主机上相同的文件，则它们是相等的。
	 * URL对象的锚不会进行比较。
     * <p>
     * This method is equivalent to:
     * <ul><code>
     *     (obj instanceof URL) &amp;&amp; sameFile((URL)obj)
     * </code></ul>
     *
     * @param   obj   the URL to compare against.
     * @return  <code>true</code> if the objects are the same;
     *          <code>false</code> otherwise.
     * @since   JDK1.0
     */
    public boolean equals(Object obj) {
	return (ref == null) ? (obj instanceof URL) && sameFile((URL)obj):
	    (obj instanceof URL) && sameFile((URL)obj) && ref.equals(((URL)obj).ref);
    }

    /** 
     * Creates an integer suitable for hash table indexing. 
     *
     * @return  a hash code for this <code>URL</code>.
     * @since   JDK1.0
     */
    public int hashCode() {

	if (hashCode == -1) {
	    hashCode = host.toLowerCase().hashCode() ^ file.hashCode() ^
		protocol.hashCode();
	}

	return hashCode;
    }
	
    /**
     * Compares the host components of two URLs.
     * @param h1 the URL of the first host to compare 
     * @param h2 the URL of the second host to compare 
     * @return	true if and only if they are equal, false otherwise.
     * @exception UnknownHostException If an unknown host is found.
     */
    boolean hostsEqual(String h1, String h2) {
	if (h1.equals(h2)) {
	    return true;
	}
	// 在比较之前必须解析地址，否则名字就像超光速子和超光速子。英格会比较不同的
	try {
	    InetAddress a1 = InetAddress.getByName(h1);
	    InetAddress a2 = InetAddress.getByName(h2);
	    return a1.equals(a2);
	} catch(UnknownHostException e) {
	} catch(SecurityException e) {
	}
	return false;
    }

    /**
     * 比较两个url，不包括“ref”字段。如果此<code>URL</code>，则返回<code>true</code>
	 * <code>other</code>参数都引用相同的资源。两个<code>URL</code>s可能不都包含相同的锚。
     *
     * @param   other   the <code>URL</code> to compare against.
     * @return  <code>true</code> if they reference the same remote object;
     *          <code>false</code> otherwise.
     * @since   JDK1.0
     */
    public boolean sameFile(URL other) {
	// AVH: should we not user getPort to compare ports?
	return protocol.equals(other.protocol) &&
	       hostsEqual(host, other.host) &&
	       (port == other.port) &&
	       file.equals(other.file);
    }

    /**
     * 构造此<code>URL</code>的字符串表示。该字符串是通过调用此对象的流协议处理程序的<code>toExternalForm</code>方法创建的。
     *
     * @return  a string representation of this object.
     * @see     URL#URL(String, String, int, String)
     * @see     URLStreamHandler#toExternalForm(URL)
     * @since   JDK1.0
     */
    public String toString() {
	return toExternalForm();
    }

    /**
     * 构造此<code>URL</code>的字符串表示。该字符串是通过调用此对象的流协议处理程序的<code>toExternalForm</code>方法创建的。
     *
     * @return  a string representation of this object.
     * @see     URL#URL(String, String, int, String)
     * @see     URLStreamHandler#toExternalForm(URL)
     * @since   JDK1.0
     */
    public String toExternalForm() {
	return handler.toExternalForm(this);
    }

    /** 
     * 返回一个<code>URLConnection</code>对象，该对象表示到<code>URL</code>所引用的远程对象的连接。
     * <p>
     * 如果还没有打开连接，则通过调用此URL的协议处理程序的<code>openConnection</code>方法来打开连接。
     *
     * @return     a <code>URLConnection</code> to the URL.
     * @exception  IOException  if an I/O exception occurs.
     * @see        URL#URL(String, String, int, String)
     * @see        URLConnection
     * @see        URLStreamHandler#openConnection(URL)
     * @since      JDK1.0
     */
    public URLConnection openConnection()
	throws IOException
    {
	return handler.openConnection(this);
    }

    /**
     * 打开到这个<code>URL</code>的连接，并返回一个<code>InputStream</code>用于从该连接读取。该方法是以下方法的简写:
     * <ul><code>
     *     openConnection().getInputStream()
     * </code></ul>
     *
     * @return     an input stream for reading from the URL connection.
     * @exception  IOException  if an I/O exception occurs.
     * @since      JDK1.0
     */
    public final InputStream openStream() 			// REMIND: drop final
	throws IOException
    {
	return openConnection().getInputStream();
    }

    /**
     * Returns the contents of this URL. This method is a shorthand for:
     * <ul><code>
     *     openConnection().getContent()
     * </code></ul>
     *
     * @return     the contents of this URL.
     * @exception  IOException  if an I/O exception occurs.
     * @see        URLConnection#getContent()
     * @since      JDK1.0
     */
    public final Object getContent() 				// REMIND: drop final
	throws IOException
    {
	return openConnection().getContent();
    }

    /**
     * The URLStreamHandler factory.
     */
    static URLStreamHandlerFactory factory;

    /**
     * 设置应用程序<code>URLStreamHandlerFactory</code>。应用程序最多只能调用该方法一次。
     * <p>
     * <code>URLStreamHandlerFactory</code>实例用于根据协议名构造流协议处理程序。
     *
     * @param      fac   the desired factory.
     * @exception  Error  if the application has already set a factory.
     * @see        URL#URL(String, String, int, String)
     * @see        URLStreamHandlerFactory
     * @since      JDK1.0
     */
    public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
	if (factory != null) {
	    throw new Error("factory already defined");
	}
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkSetFactory();
	}
	handlers.clear();
	factory = fac;
    }

    /**
     * A table of protocol handlers.
     */
    static Hashtable handlers = new Hashtable();

    /**
     * Returns the Stream Handler.
     * @param protocol the protocol to use
     */
    static synchronized URLStreamHandler getURLStreamHandler(String protocol) {
	URLStreamHandler handler = (URLStreamHandler)handlers.get(protocol);
	if (handler == null) {
	    // Use the factory (if any)
	    if (factory != null) {
		handler = factory.createURLStreamHandler(protocol);
	    }

	    // Try java protocol handler
	    if (handler == null) {
		String packagePrefixList =
		    System.getProperty(protocolPathProp, "");
		if (packagePrefixList != "") {
		    packagePrefixList += "|";
		}

		// REMIND: decide whether to allow the "null" class prefix
		// or not.
		packagePrefixList += "sun.net.www.protocol";

		StringTokenizer packagePrefixIter =
		    new StringTokenizer(packagePrefixList, "|");

		while (handler == null && packagePrefixIter.hasMoreTokens()) {
		    String packagePrefix = packagePrefixIter.nextToken().trim();
		    try {
			String clname = packagePrefix + "." + protocol
			    + ".Handler";
			handler = (URLStreamHandler)Class.forName(clname).newInstance();
		    } catch (Exception e) {
		    }
		}
	    }
	    if (handler != null) {
		handlers.put(protocol, handler);
	    }
	}
	return handler;
    }

    /**
     * 调用WriteObject将URL的状态保存到ObjectOutputStream。处理程序不会保存，因为它是特定于这个系统的。
     */
    private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws IOException
    {
	s.defaultWriteObject();	// write the fields
    }

    /**
     * 调用readObject从流中恢复URL的状态。它读取URL的组件并找到本地流处理程序。
     */
    private synchronized void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
	s.defaultReadObject();	// read the fields
	if ((handler = getURLStreamHandler(protocol)) == null) {
	    throw new IOException("unknown protocol: " + protocol);
	}
    }
}
